<?php

namespace App\Services;

use App\Exceptions\BusinessException;
use App\Helpers\AiPackChatHelper;
use App\Helpers\BailianChatHelper;
use App\Helpers\WeChatHelper;
use App\Models\AqConversation;
use App\Models\AqConversationDetail;
use App\Models\AqFile;
use App\Models\AqModel;
use App\Models\AqTool;
use App\Models\PreCommonMember;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Slowlyo\OwlAdmin\Models\AdminSetting;
use Slowlyo\OwlAdmin\Services\AdminService;

class AqService extends AdminService
{

	public $user;
	public $cookie_pre = '0710_270d_';
	public function __construct()
	{
		parent::__construct();
		$uid = $this->getUser();
		$this->user = $uid ? PreCommonMember::where('uid',$uid)->first() : [];
	}

	public function modelList()
	{
		return  AqModel::query()
			->where('enabled','1')
			->orderBy('displayorder')
			->get()->transform(function ($item){
				return [
					'id' => $item->id,
					'modelName' => $item->name,
					'remark' => "每次消耗".$item->ext_num."金币",
					'modelPrice' => $item->ext_num,
					'image_enabled' => (boolean)$item->image_enabled,
					'doc_enabled' => (boolean)$item->doc_enabled,
					'voice_enabled' => false,
					'ext_num' => $item->ext_num,
				];
			});
	}

	public function initData()
	{
		$uid = $this->user['uid'] ?? 0;
		return [
			'model_select' => true,
			'user' => [
				'id' => $uid,
				'avatar' => 'https://www.xqnmz.com/uc_server/avatar.php?uid='.$uid.'&size=middle',
				'username' => $this->user['username'] ?? '游客'
			],
			'cookie_pre' => $this->cookie_pre,
			'samples' => collect(cache()->get('aq:samples'))->pluck('text'),
			'tools' => $this->getTools(),
			'default_model_id' => (int)$this->getCookie('model_id') ? : 1,
		];
	}

	public function getTools()
	{
		return cache()->remember('aq:tools',3600,function (){
			return AqTool::query()->orderBy('custom_order')->select(['name','id'])->get();
		});
	}

	public function initShare()
	{
		$wechat = new WeChatHelper([
			'app_id' => cache()->get('aq:appid'),
			'secret' => cache()->get('aq:secret'),
		]);
		$config = $wechat->buildJsSdkConfig(
			$this->request->input('url'),
			['updateAppMessageShareData','updateTimelineShareData']
		);
		$config['title'] =  $this->request->input('title','麻醉AI助手' ) .' - 新青年';
		$config['desc'] = '麻醉科医生的专属效率工具';
		return $config;
	}

	public function updateSession()
	{
		$this->request->validate([
			'id' => 'required',
			'sessionTitle' => 'required'
		], [
			'id.required' => '请选择会话',
			'sessionTitle.required' => '请输入会话标题'
		]);
		$detail = AqConversation::query()
			->where('uid', $this->user['uid'])
			->where('id',$this->request->id)->first();
		if(!$detail){
			admin_abort('会话不存在');
		}
		AqConversation::query()->where('id',$this->request->id)->update([
			'title' => $this->request->sessionTitle
		]);
	}

	public function sessionInfo($id)
	{
		$detail = AqConversation::query()
			//->where('uid', $this->user['uid'])
			->where('id',$id)->first();
		if(!$detail){
			admin_abort('会话不存在');
		}
		return [
			'sessionTitle' => $detail->title,
			'id' => $id,
			'is_sender' => $detail->uid == ( $this->user['uid'] ?? 0 ),
			'loading_text' => Arr::get( $detail->model_data , 'loading_text','')
		];
	}

	public function deleteSession($id)
	{
		$detail = AqConversation::query()
			->where('uid', $this->user['uid'])
			->where('id',$id)->first();
		if($detail){
			return $detail->delete();
		}
		admin_abort('会话不存在');
	}

	public function getFiles()
	{
		$file_ids = array_merge(
			$this->request->input('image_ids',[]),
			$this->request->input('doc_ids',[])
		);
		$files = AqFile::query()->whereIn('id',$file_ids)->get()->keyBy('id');
		$images = $docs = [];
		foreach ($files as $file){
			if($file->isimage){
				$images [] = $file['filepath'];
			}else{
				$docs [] = [
					'filepath' => $file['filepath'],
					'filename' => $file['filename'],
				];
			}
		}
		return [
			'images' => $images,
			'docs' => $docs
		];
	}

	public function rateChat()
	{
		$id = intval($this->request->id);
		$detail = AqConversationDetail::find($id);
		if(empty($detail) || $detail->conversation->uid != $this->user['uid'])
		{
			admin_abort('会话不存在');
		}
        $rate = (float)$this->request->rate;
		$score = round($rate,1);
		if($score < 0.5 || $score > 5){
			admin_abort('评分错误');
		}
		$detail->score = $score;
		$detail->save();
	}
	public function messageList()
	{
		$id = $this->request->sessionId;
		$detail = AqConversation::query()
			//->where('uid', $this->user['uid'] )
			->where('id',$id)->first();
		if(!$detail){
			admin_abort('会话不存在');
		}
		$list = [];
		$model = AqConversationDetail::query()
			->where('conversation_id',$id)
			->orderBy('id');
		if($detail['uid'] != ($this->user['uid'] ?? 0)){
			$model->limit(2);
		}
		$all = $model->get();
		$file_ids = [];
		$all->each(function ($item) use (&$file_ids){
			$file_ids = array_merge($file_ids,$item->request_data['doc_ids']);
			$file_ids = array_merge($file_ids,$item->request_data['image_ids']);
		});
		$files = AqFile::query()->whereIn('id',$file_ids)->get()->keyBy('id');
		$all->each(function ($item) use (&$list,$files,$detail){
				$images = $docs = [];
				if(!empty($item->request_data['image_ids'])){
					foreach ($item->request_data['image_ids'] as $fileid){
						if(empty($files[$fileid])) continue;
						if($file = Arr::get($files,$fileid)){
							$images [] = $file['filepath'];
						}
					}
				}
				if(!empty($item->request_data['doc_ids'])){
					foreach ($item->request_data['doc_ids'] as $fileid){
						if(empty($files[$fileid])) continue;
						if($file = Arr::get($files,$fileid)){
							$docs[] = [
								'filepath' => $file['filepath'],
								'filename' => $file['filename'],
							];
						}
					}
				}

				$list[] = [
					'id' => $item->id. '_user',
					'avatar' => 'https://www.xqnmz.com/uc_server/avatar.php?uid='.$detail->uid.'&size=middle',
					'role' => 'user',
					'content' => $item->request_text,
					'images' => $images,
					'docs' => $docs,
					'rate' => 5,
					'completed' => true
				];
				$list[] = [
					'id' => $item->id.'_assistant',
					'role' => 'system',
					'content' => $item->response_text ? : '[中断]',
					'images' => [],
					'docs' => [],
					'rate' => $item->score,
					'completed' => (
						$detail->uid == ( $this->user['uid'] ?? 0 )
						? Arr::get(Arr::last($item->response_data),'output.finish_reason') == 'stop'
						: false
					),
				];
			});
		return $list;
	}

	public function createSession()
	{
		list($params,$model) = $this->check(false);
		$id = Str::uuid();
		AqConversation::firstOrCreate([
			'id' => $id
		],[
			'model_id' => $model->id,
			'uid' => $this->user['uid'],
			'model_data' => $model,
			'title' => $params['sessionTitle']
		]);
		return $id;
	}

	public function clearSession($modelId)
	{
		return AqConversation::where('uid',$this->user['uid'])
			->whereRaw('model_data->\'$.id\' = '.$modelId)
			->delete();
	}
	public function uploadFile()
	{
		if ($this->request->hasFile('file') && $this->request->file('file')->isValid()) {
			$file = $this->request->file('file');
			//存储到PUBLIC
			$storepath = $file->store('images/'.date('Ymd'),['disk'=>'public']);
			if(!$storepath){
				admin_abort('上传失败');
			}
			$filename = $file->getClientOriginalName();
			$fileext = strtolower(pathinfo($storepath, PATHINFO_EXTENSION));
			if(!in_array($fileext, ['gif','png','jpg','jpeg','pdf','doc','docx','xls','xlsx','ppt','pptx','txt','md'])){
				admin_abort('上传文件格式错误');
			}
			$data = [];
			$data['filename'] = $filename;
			$data['filepath'] = $storepath;
			$data['filesize'] = $file->getSize();
			$data['fileext'] = $fileext;
			if(in_array($fileext, ['gif','png','jpg','jpeg'])){
				$data['isimage'] = 1;
			}
			$attach = AqFile::create($data);
			return [[
				'url' => $attach->filepath,
				'id' => $attach->id,
			]];
		}else{
			admin_abort('请上传文件');
		}
	}

	public function check($send = true)
	{
		$params = $this->request->all();
		//判断用户是否登录
		if(!$this->user){
			if($send){
				BailianChatHelper::eventsourceClose('[请先登录](https://www.xqnmz.com/member.php?mod=logging&action=login)');
			}else{
				admin_abort('请先登录');
			}
		}
		//获取模型
		$model = AqModel::query()->find($params['modelId'] ?? 0);
		if(!$model){
			if($send){
				BailianChatHelper::eventsourceClose('请求模型不存在，请返回重试');
			}else{
				admin_abort('请求模型不存在，请返回重试');
			}
		}
		//判断用户组
		if(!in_array($this->user['groupid'],$model->group_ids)){
			$tip = AdminSetting::where('key','aq_tip')->value('values');
			if($send){
				BailianChatHelper::eventsourceClose($tip);
			}else{
				admin_abort($tip);
			}
		}
		return [$params,$model];
	}
	public function sessionList()
	{
		$query = $this->request->all();
		return AqConversation::query()
			->with(['details' => function ($query){
				$query->where('score','>',3);
			}])
			->where('uid',$this->user['uid'])
			->when(Arr::get($query,'modelId'),function ($query,$model_id){
				$query->whereRaw('model_data->\'$.id\' = '.$model_id);
			})
			->select(['id','title'])
			->orderBy(
				 'created_at' ,
				'desc'
			)
			->get()->transform(function ($item){
				return [
					'id' => $item->id,
					'content' => $item->title,
					'digest' => $item->details->count() ? true : false,
				];
			});
	}
	public function sendChat()
	{
		$params = $this->request->all();
		BailianChatHelper::eventStart(Arr::get($params,'sessionId'));
		list($params,$model) = $this->check();
		//判断内容
		$params['messages'] = Arr::last(Arr::get($params,'messages',[]));
		$message = Arr::get($params,'messages.content');
		if(!$message){
			BailianChatHelper::eventsourceClose('请输入内容');
		}
        // 获取当前用户支付信息
        $ext_num = $model->ext_num;
        $isPlus = $model->text_model == 'qwen-max' ? 1 : 0;
        $payInfo = AiPackChatHelper::getPayInfo($this->user['uid'],$isPlus,$ext_num);
        $ext_num = $payInfo['ext'];
		//判断金币是否足够
		if($this->user->count['extcredits2'] < $ext_num)
		{
			BailianChatHelper::eventsourceClose('请点击：<a href="https://www.xqnmz.com/home.php?mod=spacecp&ac=credit&op=buy" style="text-decoration-color:#0AE!important;color:#0AE">金币充值（支付宝）</a> 或 <a href="https://shop18389069.m.youzan.com/v2/tag/1fhqjv1mt?shopAutoEnter=1" style="text-decoration-color:#07C160!important;color:#07C160">积分购买（微信）</a>');
		}
		//创建conversation
		$id = BailianChatHelper::getId();
		$conversation = AqConversation::find($id);
		$detail = AqConversationDetail::create([
			'conversation_id' => $id,
			'request_text' => $message,
			'request_data' => $params,
			'tool_id' => Arr::get($params,'toolId',0),
		]);
		if($detail->tool)
		{
			$delimiter = str_repeat('-',60);
			//构建工具数据
			$message = str_replace(
				['{form_data}','{output_text}'],
				["{$delimiter}\n{$message}\n{$delimiter}",$detail->tool->output_text],
				$detail->tool->prompt
			);
		}

		cache()->put('conversation:text:'.$detail->id,'');
		cache()->put('conversation:data:'.$detail->id,[]);
		register_shutdown_function(function() use($detail) {
			if (connection_aborted()) {
				BailianChatHelper::finished($detail->id,true);
			}
		});
		BailianChatHelper::eventSourceData(['id' => $detail->id]);
        if($payInfo['pack'] == 'self')
        {
            AiPackChatHelper::decrementUserNum($this->user['uid'],$isPlus);
        }
        elseif($payInfo['pack'] == 'group')
        {
            AiPackChatHelper::decrementGroupNumByUid($this->user['uid'],$isPlus);
        }elseif($ext_num){
			$data = [$this->user['uid'],['2'=>-$ext_num],true,'','0','AI问答','AI问答','AI问答'];
			$data = json_encode($data);
			$bizContent = $this->authcode($data,'ENCODE','dzlab');
			$api = 'https://www.xqnmz.com/api/credit.php?bizContent='.rawurlencode($bizContent);
			$res = file_get_contents($api);
			if($res != 'success')
			{
				\Log::error($res);
				BailianChatHelper::eventsourceClose('扣除金币失败，请联系管理员');
			}
			BailianChatHelper::eventSourceEvent('金币 - '.$ext_num);
		}
		//根据对话重新获取模型
		$model = AqModel::find($conversation->model_data['id']);
		//构建图片
		$imageText = '';
		if($imageIds =  Arr::get($params,'image_ids',[]))
		{
			$images = AqFile::whereIn('id',$imageIds)->where('isimage',1)
				->selectRaw('filepath as path,filepath,filename')->get()->each(function ($item) {
					$item->path = Storage::disk('public')->path($item->path);
				})->toArray();
			$imageText = BailianChatHelper::parseImage($images,$model);
			if(!$imageText){
				BailianChatHelper::eventsourceClose('解析图片失败');
			}
		}
		//构建文档提示词
		$file_ids = [];
		if($docIds =  Arr::get($params,'doc_ids',[]))
		{
			$docs = AqFile::whereIn('id',$docIds)->where('isimage',0)
				->selectRaw('filepath as path,filepath,filename')->get()->each(function ($item) {
					$item->path = Storage::disk('public')->path($item->path);
				})->toArray();
			try {
				$file_ids = BailianChatHelper::parseDocument($docs,$model);
			}catch (BusinessException $exception)
			{
				BailianChatHelper::eventsourceClose($exception->getMessage());
			}
		}
		//构建最终的提示词
		$promot = str_replace(
			['{prompt}','{image_text}'],
			[$message,$imageText],
			$model->prompt
		);
		//发送请求
		$data = [
			"input" => [
				"prompt" => $promot,
			],
			"parameters" => [
				'incremental_output' => true,
			]
		];
		if($file_ids){
			$data['parameters']['rag_options']['session_file_ids'] = $file_ids;
		}
		if($conversation->session_id){
			$data['input']['session_id'] = $conversation->session_id;
		}
		$detail->update([
			'api_data' => $data,
		]);
		BailianChatHelper::send($model->api_key, $model->app_id,$data,$detail->id);
		BailianChatHelper::finished($detail->id);
		$text = cache()->get('conversation:text:'.$detail->id);
		$data = cache()->get('conversation:data:'.$detail->id);
		AqConversationDetail::query()
			->where('id',$detail->id)
			->update([
				'response_text' => $text,
				'response_data' => $data,
			]);

	}

	private function getUser()
	{
		$auth = $this->getCookie('auth');
		if(!$auth){
			return 0;
		}
		$auth = $this->authcode($auth);
		if(!$auth){
			return 0 ;
		}
		$auth = explode("\t", $auth);
		return intval($auth[1] ?? 0);
	}

	private function getCookie($key = '')
	{
		$cookiePre = $this->cookie_pre;
		$cookieKeys = array_filter(array_keys($_COOKIE), function ($item) use ($cookiePre) {
			return strpos($item, $cookiePre) === 0;
		});
		$cookies = array_map(function ($item) use ($cookiePre) {
			return [
				'name' => str_replace($cookiePre, '', $item),
				'value' => $_COOKIE[$item]
			];
		}, $cookieKeys);
		$cookies = collect($cookies)->pluck('value','name')->toArray();
		if($key){
			return $cookies[$key] ?? false;
		}else{
			return $cookies;
		}
	}
	private function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0, $ckey_length = 4) {
		// 动态密钥长度, 通过动态密钥可以让相同的 string 和 key 生成不同的密文, 提高安全性
		$key = md5($key != '' ? $key : md5('f4f288FTO0M5n2rX'.$this->getCookie('saltkey')));
		// a参与加解密, b参与数据验证, c进行密文随机变换
		$keya = md5(substr($key, 0, 16));
		$keyb = md5(substr($key, 16, 16));
		$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length) : substr(md5(microtime()), -$ckey_length)) : '';

		// 参与运算的密钥组
		$cryptkey = $keya.md5($keya.$keyc);
		$key_length = strlen($cryptkey);

		// 前 10 位用于保存时间戳验证数据有效性, 10 - 26位保存 $keyb , 解密时通过其验证数据完整性
		// 如果是解码的话会从第 $ckey_length 位开始, 因为密文前 $ckey_length 位保存动态密匙以保证解密正确
		$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
		$string_length = strlen($string);

		$result = '';
		$box = range(0, 255);

		// 产生密钥簿
		$rndkey = [];
		for($i = 0; $i <= 255; $i++) {
			$rndkey[$i] = ord($cryptkey[$i % $key_length]);
		}

		// 打乱密钥簿, 增加随机性
		// 类似 AES 算法中的 SubBytes 步骤
		for($j = $i = 0; $i < 256; $i++) {
			$j = ($j + $box[$i] + $rndkey[$i]) % 256;
			$tmp = $box[$i];
			$box[$i] = $box[$j];
			$box[$j] = $tmp;
		}

		// 从密钥簿得出密钥进行异或，再转成字符
		for($a = $j = $i = 0; $i < $string_length; $i++) {
			$a = ($a + 1) % 256;
			$j = ($j + $box[$a]) % 256;
			$tmp = $box[$a];
			$box[$a] = $box[$j];
			$box[$j] = $tmp;
			$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
		}

		if($operation == 'DECODE') {
			// 这里按照算法对数据进行验证, 保证数据有效性和完整性
			// $result 01 - 10 位是时间, 如果小于当前时间或为 0 则通过
			// $result 10 - 26 位是加密时的 $keyb , 需要和入参的 $keyb 做比对
			if(((int)substr($result, 0, 10) == 0 || (int)substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) === substr(md5(substr($result, 26).$keyb), 0, 16)) {
				return substr($result, 26);
			} else {
				return '';
			}
		} else {
			// 把动态密钥保存在密文里, 并用 base64 编码保证传输时不被破坏
			return $keyc.str_replace('=', '', base64_encode($result));
		}

	}

	public function download($id)
	{
		$file = AqFile::find($id);
		if(!$file){
			//http响应404
			header('HTTP/1.1 404 Not Found');
		}else{
			return $file->filepath;
		}
	}

	public function getTool($id)
	{
		$tool = AqTool::find($id);
		if(!$tool){
			admin_abort('集成工具不存在');
		}
		return Arr::only($tool->toArray(),['id','name','form_rule']);
	}

	public function getToolMessage($id,$replace = false)
	{
		$tool = AqTool::find($id);
		if(!$tool){
			admin_abort('集成工具不存在');
		}
		$data = '';
		foreach ($this->request->all() as $key => $value){
			$data .= $key.':'.$value."\n";
		}
		if($replace){
			$message = str_replace(
				['{form_data}','{output_text}'],
				[$data,$tool->output_text],
				$tool->prompt
			);
		}else{
			$message = $data;
		}
		return [
			'message' => $message,
			'tool_id' => $tool->id,
		];
	}

	public function getDevice()
	{
		$code = $this->authcode($this->request->input('auth'),'DECODE','ai.xqnmz.dzlab.dev');
		if(!$code)
		{
			dd('未经授权的设备',$this->request->all());
		}else{
			return '设备：'.$code;
		}
	}
}
